#include "ui_mover.h"
#include "ui_widget.h"
#include "ui_progress.h"
#include "ui_manager.h"

#include "part2d/part2d_cache.h"
#include "math/vec4.h"
#include <algorithm>
#include <cassert>
#include "console/console.h"
#include "math/math_core.h"
#include "lang/lang.h"

#include "util/timer.h"		    // Needs to come in early
#include "util/mmgr.h"		    // Needs to come in early
#include "util/common_macros.h"		// macros
#include "util/log.h"		    // logging

#include "script/scripthelper.h"
#include <algorithm>


#ifdef _ENABLE_LUABIND
#include <luabind/luabind.hpp>
#include <luabind/discard_result_policy.hpp>

#endif // _ENABLE_LUABIND

using namespace ui;
using namespace script;

// KEITH: implemented Shared ImageCache for all UIMovers
// TODO: implement make ImageCache same size as widget
// TODO: implement pooling of ImageCache to prevent fragmentation
// TODO: this implementation only works for one UIMover at a time, still need to fix this

class UIMoverCache
{
public:
	static UIMoverCache* GetInstance()
	{
		if (!m_Instance)
		{
			m_Instance = new UIMoverCache();
			m_Instance->m_pCache = 0;
		}
		return m_Instance;
	}

	ImageCache* GetImageCache(math::Vec2 Size)
	{
		if (!m_pCache)
		m_pCache = GetGraphics()->CreateCache(1024, 1024, 32); // maybe should hardcode, since texture should be pow-of-2
		return m_pCache;
	}

private:
	struct UIMoverCacheStruct
	{
		ImageCache*		m_pCache;
		bool			m_IsUsed;
		math::Vec2		Size;
	};

	UIMoverCache(){};
	~UIMoverCache(){UTIL_SAFE_DELETE(m_pCache)};
	static UIMoverCache*				m_Instance;
	ImageCache*							m_pCache;
	std::vector<UIMoverCacheStruct*>	m_Cache;
};

UIMoverCache* UIMoverCache::m_Instance = NULL;


//////////////////////////////////////
// implementation classes
class UIMoveProgress : public UIMover
{
public:
	static UIMover*	AddMoveProg(UIWidget*		pTarget, float Time, float From, float To,  const std::string&  Action)
	{
		if (!pTarget || pTarget->GetType() != UIWidget::WT_PROGRESS) return NULL; 
		UIMoveProgress* pNew = new UIMoveProgress((UIWidget*)pTarget, 0, Time, true);
		pNew->m_From = From;
		pNew->m_To	 = To;
		pNew->m_Action = Action;
		pTarget->AddMover(pNew);
		return pNew;
	}
private:
	UIMoveProgress(UIWidget*		pTarget, float Delay, float Time, bool DoDrop) : UIMover(pTarget, Delay, Time, DoDrop) {};
	virtual void	OnAction(float dt)
	{
		UIProgress* pProg = (UIProgress*)m_pTarget;
		float Delta = (m_To-m_From) * m_Countdown.GetProgress() ;
		pProg->SetProgress(m_From + Delta);
	}
	virtual void	OnCountdown()
	{
		UIProgress* pProg = (UIProgress*)m_pTarget;
		pProg->SetProgress(m_To);
		UIMover::OnCountdown();
	}
	float	m_From, m_To;
};

// sets Mouse Off image
class UISetImage : public UIMover
{
public:
	static UIMover*	AddSetImage(UIWidget*		pTarget, float Delay, const std::string& Image,  const std::string&  Action)
	{
		if (!pTarget) return NULL; 
		UISetImage* pNew = new UISetImage(pTarget, Delay, Image);
		pNew->m_Image = Image;
		pNew->m_Action = Action;
		pTarget->AddMover(pNew);
		return pNew;
	}
private:
	UISetImage(UIWidget*		pTarget, float Delay, const std::string& Image) : UIMover(pTarget, Delay, -1, true) {};
	virtual void	OnAction(float dt)
	{
	}
	virtual void	OnCountdown()
	{
		m_pTarget->SetOffImage(m_Image);
		UIMover::OnCountdown();
	}
	std::string			m_Image;
};


class UIXFade : public UIMover
{
public:
	static UIMover*	AddUIXFade(UIWidget*		pTarget, float Delay, float Time, const math::Vec2& AlphaSet,  const std::string&  Action)
	{
		if (!pTarget) return NULL; 
		UIXFade* pNew = new UIXFade(pTarget, Delay, Time, true);
		COLOR c = pTarget->GetColor();
		math::Vec3		CurrentCol(ColorGetRf(c), ColorGetGf(c), ColorGetBf(c));

		pNew->m_From = math::Vec4(CurrentCol.x, CurrentCol.y, CurrentCol.z, AlphaSet.x);
		pNew->m_To	 = math::Vec4(CurrentCol.x, CurrentCol.y, CurrentCol.z, AlphaSet.y);
		pNew->m_Action = Action;
		pTarget->AddMover(pNew);
		return pNew;
	}
private:
	UIXFade(UIWidget*		pTarget, float Delay, float Time, bool DoDrop) : UIMover(pTarget, Delay, Time, DoDrop) {};
	virtual void	OnAction(float dt)
	{
		math::Vec4	Delta = (m_To-m_From) * m_Countdown.GetProgress() ;
		m_pTarget->SetColor(m_From.x + Delta.x, m_From.y + Delta.y, m_From.z + Delta.z, m_From.w + Delta.w);
	}
	virtual void	OnCountdown()
	{
		m_pTarget->SetColor(m_To.x, m_To.y, m_To.z, m_To.w);
		UIMover::OnCountdown();
	}
	math::Vec4		m_From, m_To;
};


class UIScript : public UIMover
{
public:
	static UIMover*	AddUIScript(UIWidget*		pTarget, const std::string& Script)
	{
		if (!pTarget) return NULL; 
		UIScript* pNew = new UIScript(pTarget, Script);
		pTarget->AddMover(pNew);
		return pNew;
	}
private:
	UIScript(UIWidget*		pTarget, const std::string& Script) :
		UIMover(pTarget, 0, 0)
	{
		m_Action = Script;
	};
	virtual void	OnAction(float dt)
	{
	}
};

class UIHide : public UIMover
{
public:
	static UIMover*	AddUIHide(UIWidget*		pTarget)
	{
		if (!pTarget) return NULL; 
		UIHide* pNew = new UIHide(pTarget);
		pTarget->AddMover(pNew);
		return pNew;
	}
private:
	UIHide(UIWidget*		pTarget) :
		UIMover(pTarget, 0, 0)
	{
	};
	virtual void	OnAction(float dt)
	{
	}
	virtual void	OnCountdown()
	{
		if (!m_Ended)
		{
			m_Ended = true;
			m_Invalidated = true;
			m_pTarget->SetIsVisible(false);
			UIMover::OnCountdown();
		}
	}
};

class UIPan : public UIMover
{
public:
	static UIMover*	AddUIPan(UIWidget*		pTarget, float Delay, float Time, const math::Vec2& To,  const std::string&  Action)
	{
		if (!pTarget) return NULL; 
		UIPan* pNew = new UIPan(pTarget, Delay, Time, true);
		pNew->m_From = pTarget->GetPosition();
		pNew->m_To	 = To;
		pNew->m_Action = Action;
		pTarget->AddMover(pNew);
		return pNew;
	}
private:
	UIPan(UIWidget*		pTarget, float Delay, float Time, bool DoDrop) : UIMover(pTarget, Delay, Time, DoDrop) {};
	virtual void	OnAction(float dt)
	{
		math::Vec2	Delta = (m_To-m_From) * m_Countdown.GetProgress() ;
		m_pTarget->SetPosition(m_From+Delta);	//m_pTarget->GetPosition()
	}
	virtual void	OnCountdown()
	{
		m_pTarget->SetPosition(m_To);
		UIMover::OnCountdown();
	}
	math::Vec2			m_From, m_To;
};


// hack: allows us to pack From->To into a single Vec4 to fit it under the max 5 arguments for LuaBind
class UIMove : public UIMover
{
public:
	
	static UIMover*	AddUIMove(UIWidget*		pTarget, float Delay, float Time, const math::Vec4& FromTo,   const std::string&  Action)
	{
		if (!pTarget) return NULL; 
		UIMove* pNew = new UIMove(pTarget, Delay, Time, true);
		pNew->m_From = math::Vec2(FromTo.x, FromTo.y);
		pNew->m_To	 = math::Vec2(FromTo.z, FromTo.w);
		pNew->m_Action = Action;
		pTarget->AddMover(pNew);
		return pNew;
	}

private:
	UIMove(UIWidget*		pTarget, float Delay, float Time, bool DoDrop) : UIMover(pTarget, Delay, Time, DoDrop) {};
	virtual void	OnAction(float dt)
	{
		math::Vec2	Delta = (m_To-m_From) * m_Countdown.GetProgress() ;
		m_pTarget->SetPosition(m_From+Delta);	//m_pTarget->GetPosition()
	}
	virtual void	OnCountdown()
	{
		m_pTarget->SetPosition(m_To);
		UIMover::OnCountdown();
	}
	math::Vec2			m_From, m_To;
};

class UISine : public UIMover
{
public:
	static UIMover*	AddUISine(UIWidget*		pTarget, const math::Vec3& X, const math::Vec3& Y)
	{
		if (!pTarget) return NULL; 
		UISine* pNew		= new UISine(pTarget, 0, -1, true);
		pNew->m_From	= pTarget->GetPosition();
		pNew->m_Amp		= math::Vec2(X.x, Y.x);
		pNew->m_Period	= math::Vec2(X.y, Y.y);
		pNew->m_Offset.x = (X.z/X.y) * (2.0f*MATH_PI);
		pNew->m_Offset.y = (Y.z/Y.y) * (2.0f*MATH_PI);
		pTarget->AddMover(pNew);
		return pNew;
	}
private:
	UISine(UIWidget*		pTarget, float Delay, float Time, bool DoDrop) : UIMover(pTarget, Delay, Time, DoDrop), m_Time(0) {};
	virtual void	OnAction(float dt)
	{
		m_Time+=dt;
		float	dtx = (m_Time / m_Period.x) * (2.0f*MATH_PI);
		float	dty = (m_Time / m_Period.y) * (2.0f*MATH_PI);
		math::Vec2	Delta(	sinf(m_Offset.x + dtx) * m_Amp.x,
							sinf(m_Offset.y + dty) * m_Amp.y);
		m_pTarget->SetPosition(m_From+Delta);	
	}
	virtual void	OnCountdown()
	{
		m_pTarget->SetPosition(m_From);
		UIMover::OnCountdown();
	}
	math::Vec2			m_From;
	math::Vec2			m_Amp, m_Period, m_Offset;
	float				m_Time;
};

// hack: allows us to pack Delay, Time into a single Vec2 to fit it under the max 5 arguments for LuaBind
class UIPartFade : public UIMover
{
public:
	static UIMover*	AddUIPartFade(UIWidget* pTarget, math::Vec2 TimeSet, math::Vec2 AlphaSet, const std::string& Part, const std::string& Action)
	{
		if (!pTarget) return NULL; 
		UIPartFade* pNew = new UIPartFade(pTarget, TimeSet.x, TimeSet.y, Part);
		COLOR c = pTarget->GetColor();
		math::Vec3		CurrentCol(ColorGetRf(c), ColorGetGf(c), ColorGetBf(c));

		pNew->m_From = math::Vec4(CurrentCol.x, CurrentCol.y, CurrentCol.z, AlphaSet.x);
		pNew->m_To	 = math::Vec4(CurrentCol.x, CurrentCol.y, CurrentCol.z, AlphaSet.y);
		pNew->m_Action = Action;
		pNew->m_FadeIn = AlphaSet.x < AlphaSet.y;
		pTarget->AddMover(pNew);
		pTarget->SetColor(CurrentCol.x, CurrentCol.y, CurrentCol.z, AlphaSet.x);
		return pNew;
	}
	virtual void	RenderForeground()
	{
		if (m_pPart) m_pPart->Render(math::Vec2(), 1);
	}
private:
	UIPartFade(UIWidget* pTarget, float Delay, float Time, const std::string& Part) : UIMover(pTarget, Delay, Time, false),
		m_pPart(NULL),
		m_PartCount(0),
		m_Triggered(false)
	{
		m_RenderChildren = false;
		pTarget->SetIsActive(false);
		//if (!Part.empty() && m_Part.Load(Part) && (m_Part.GroupC() > 0))
		if (!Part.empty())
		{
			m_pPart = PART_CACHE().GetPart(Part);
			if (m_pPart && (m_pPart->GroupC() > 0))
			{
				m_PartCount = m_pPart->GetGroup(0)->GetMaxC() / 4;
			}
		}
	}
	virtual ~UIPartFade()
	{
		m_pTarget->SetIsActive(true);
		RELEASE_PART(m_pPart);
	}
	virtual void	OnAction(float dt)
	{
		if (!m_Invalidated)
		{
			math::Vec4	Delta = (m_To-m_From) * m_Countdown.GetProgress() ;
			m_pTarget->SetColor(m_From.x + Delta.x, m_From.y + Delta.y, m_From.z + Delta.z, m_From.w + Delta.w);
			if (!m_Triggered && !m_FadeIn)
			{
				TriggerParticles();
				m_Triggered = true;
			}
			if (m_pPart) m_pPart->Tick(dt);
		}
	}
	virtual void	OnCountdown()
	{
		if (!m_Invalidated)
		{
			m_pTarget->SetColor(m_To.x, m_To.y, m_To.z, m_To.w);
			if (m_FadeIn)
			{
				// for fade in, restore UI the moment animation is complete
				m_pTarget->SetIsActive(true);
				m_RenderChildren = true;
			}
			else
			{
				// keep refreshing active status in case something else screws with it
				m_pTarget->SetIsActive(false);
			}
			if (!m_Triggered && m_FadeIn)
			{
				TriggerParticles();
				m_Triggered = true;
				m_Ended = false;
				return;
			}
			else if (m_pPart && m_pPart->GroupC() && (m_pPart->GetGroup(0)->GetPartC() > 0))
			{
				m_Ended = false;
				return;
			}
		}
		UIMover::OnCountdown();
	}
	void	TriggerParticles()
	{
		if (!m_pPart) return;
		math::Vec2 From = m_pTarget->GetPosition();
		math::Vec2 To = From + m_pTarget->GetSize();
		for (int i = 0; i < m_PartCount; i++)
		{
			m_pPart->TriggerParticlesOnLine( From, math::Vec2(To.x, From.y) );
			m_pPart->TriggerParticlesOnLine( math::Vec2(To.x, From.y), To );
			m_pPart->TriggerParticlesOnLine( To, math::Vec2(From.x, To.y) );
			m_pPart->TriggerParticlesOnLine( math::Vec2(From.x, To.y), From );
		}
	}
	math::Vec4		m_From, m_To;
	part2d::Part2D*	m_pPart;
	int				m_PartCount;
	bool			m_Triggered;
	bool			m_FadeIn;
};

// hack: allows us to pack Delay, Time into a single Vec2 to fit it under the max 5 arguments for LuaBind
class UIPartZoom : public UIMover
{
public:
	static UIMover* AddUIPartZoom(UIWidget* pTarget, math::Vec2 TimeSet, bool ZoomIn, const std::string& Part, const std::string& Action)
	{
		if (!pTarget) return NULL;
		UIPartZoom* pNew = new UIPartZoom(pTarget, TimeSet.x, TimeSet.y, ZoomIn, Part);
		pNew->m_Action = Action;
		pTarget->AddMover(pNew);
		return pNew;
	};
	virtual void RenderForeground()
	{
		if (!m_Countdown.IsReady())
			m_PanelSprite.Render();

		if (m_pPart) m_pPart->Render(math::Vec2(), 1);
	}
private:
	UIPartZoom(UIWidget* pTarget, float Delay, float Time, bool ZoomIn, const std::string& Part) : UIMover(pTarget, Delay, Time, false),
			m_pPart(NULL),
			m_PartCount(0),
			m_Triggered(false),
			m_ZoomIn(ZoomIn)
	{
		COLOR c = pTarget->GetColor();
		m_BaseColor = math::Vec4( ColorGetRf(c), ColorGetGf(c), ColorGetBf(c), ColorGetAf(c) );

		m_RenderChildren = false;
		m_RenderSelf = false;

		//if (!Part.empty() && m_Part.Load(Part) && (m_Part.GroupC() > 0))
		if (!Part.empty())
		{
			m_pPart = PART_CACHE().GetPart(Part);
			if (m_pPart && (m_pPart->GroupC() > 0))
				m_PartCount = m_pPart->GetGroup(0)->GetMaxC() / 4;
		}

		if (UISprite* pSprite = pTarget->GetSprite(UIWidget::WS_MOUSE_OFF))
		{
			m_PanelSprite.Create(pSprite->GetFilename());
			m_PanelSprite.m_Pos = m_Pos = pTarget->GetPosition();
			m_PanelSprite.m_Size = m_Size = pTarget->GetSize();
			m_PanelSprite.m_Color = ColorfRGBA(m_BaseColor.x, m_BaseColor.y, m_BaseColor.z, m_ZoomIn ? 0.0f : m_BaseColor.w);
		}
		else
		{
			Invalidate();
			return;
		}

		pTarget->SetIsActive(false);
	}
	virtual ~UIPartZoom()
	{
		m_pTarget->SetIsActive(true);
		RELEASE_PART(m_pPart);
	}
	virtual void	OnAction(float dt)
	{
		if (!m_Invalidated)
		{
			float Progress = m_Countdown.GetProgress();
			float Scale = m_ZoomIn ? Progress : 1.0f - Progress;
			
			math::Vec2 Pos = m_Pos + (0.5f * m_Size);
			math::Vec2 Size = Scale * m_Size;
			Pos -= (0.5f * Size);

			m_PanelSprite.m_Pos = Pos;
			m_PanelSprite.m_Size = Size;
			m_PanelSprite.m_Color = ColorfRGBA(m_BaseColor.x, m_BaseColor.y, m_BaseColor.z, m_BaseColor.w * Scale);

			if (!m_Triggered && !m_ZoomIn)
			{
				TriggerParticles();
				m_Triggered = true;
			}
			if (m_pPart) m_pPart->Tick(dt);
		}
	}
	virtual void	OnCountdown()
	{
		if (!m_Invalidated)
		{
			if (m_ZoomIn)
			{
				// for zoom in, restore UI the moment animation is complete
				m_pTarget->SetIsActive(true);
				m_RenderSelf = true;
				m_RenderChildren = true;
			}
			else
			{
				// keep refreshing active status in case something else screws with it
				m_pTarget->SetIsActive(false);
			}
			if (!m_Triggered && m_ZoomIn)
			{
				TriggerParticles();
				m_Triggered = true;
				m_Ended = false;
				return;
			}
			else if (m_pPart && m_pPart->GroupC() && (m_pPart->GetGroup(0)->GetPartC() > 0))
			{
				m_Ended = false;
				return;
			}
		}
		UIMover::OnCountdown();
		//m_pTarget->SetIsActive(true); let destructor do this
	}
	void	TriggerParticles()
	{
		if (!m_pPart) return;

		math::Vec2 From = m_pTarget->GetPosition();
		math::Vec2 To = From + m_pTarget->GetSize();
		for (int i = 0; i < m_PartCount; i++)
		{
			m_pPart->TriggerParticlesOnLine( From, math::Vec2(To.x, From.y) );
			m_pPart->TriggerParticlesOnLine( math::Vec2(To.x, From.y), To );
			m_pPart->TriggerParticlesOnLine( To, math::Vec2(From.x, To.y) );
			m_pPart->TriggerParticlesOnLine( math::Vec2(From.x, To.y), From );
		}
	}
	part2d::Part2D*	m_pPart;
	UISprite		m_PanelSprite;
	int				m_PartCount;
	math::Vec4		m_BaseColor;
	math::Vec2		m_Pos;
	math::Vec2		m_Size;
	bool			m_Triggered;
	bool			m_ZoomIn;
};

// hack: allows us to pack Delay, Time into a single Vec2 to fit it under the max 5 arguments for LuaBind
class UIPartScale : public UIMover
{
public:
	static UIMover* AddUIPartScale(UIWidget* pTarget, math::Vec2 TimeSet, bool ZoomIn, const std::string& Part, const std::string& Action)
	{
		if (!pTarget) return NULL;
		UIPartScale* pNew = new UIPartScale(pTarget, TimeSet.x, TimeSet.y, ZoomIn, Part);
		pNew->m_Action = Action;
		pTarget->AddMover(pNew);
		return pNew;
	};
	virtual void RenderForeground()
	{
		if (!m_Countdown.IsReady())
		{
			RenderCache(255,255,255,255,false);
		}

		if (m_pPart) m_pPart->Render(math::Vec2(), 1);
	}
private:
	UIPartScale(UIWidget* pTarget, float Delay, float Time, bool ZoomIn, const std::string& Part) : UIMover(pTarget, Delay, Time, false),
			m_pPart(NULL),
			m_PartCount(0),
			m_Triggered(false),
			m_ZoomIn(ZoomIn)
	{
		COLOR c = pTarget->GetColor();
		m_BaseColor = math::Vec4( ColorGetRf(c), ColorGetGf(c), ColorGetBf(c), ColorGetAf(c) );

		m_RenderChildren = false;
		m_RenderSelf = false;

		//if (!Part.empty() && m_Part.Load(Part) && (m_Part.GroupC() > 0))
		if (!Part.empty())
		{
			m_pPart = PART_CACHE().GetPart(Part);
			if (m_pPart && (m_pPart->GroupC() > 0))
				m_PartCount = m_pPart->GetGroup(0)->GetMaxC() / 4;
		}

		if (UISprite* pSprite = pTarget->GetSprite(UIWidget::WS_MOUSE_OFF))
		{
			m_PanelSprite.Create(pSprite->GetFilename());
			m_PanelSprite.m_Pos = m_Pos = pTarget->GetPosition();
			m_PanelSprite.m_Size = m_Size = pTarget->GetSize();
			m_PanelSprite.m_Color = ColorfRGBA(m_BaseColor.x, m_BaseColor.y, m_BaseColor.z, m_ZoomIn ? 0.0f : m_BaseColor.w);
		}
		else
		{
			Invalidate();
			return;
		}

		pTarget->SetIsActive(false);

		// KEITH: create image cache
		//m_pCache = UIMoverCache::GetInstance()->GetImageCache(pTarget->GetSize());
		//temp
		m_pCache = GetGraphics()->CreateCache(1024, 1024, 32); // maybe should hardcode, since texture should be pow-of-2

		// KEITH: setup contents of cache
		if (m_pCache->BeginCache())
		{
			m_pCache->ClearCache( ColorRGBA(255,255,255,0) );
			
			math::Vec2 temppos;
			temppos.x = pTarget->GetPosition().x;
			temppos.y = pTarget->GetPosition().y;

			pTarget->SetPosition(math::Vec2(0.0f,0.0f));//set to 0,0 for imagecache
			pTarget->Render();
			pTarget->SetPosition(temppos);//reset position

			m_pCache->EndCache();
			m_HasCached = true;
		}
	}
	virtual ~UIPartScale()
	{
		m_pTarget->SetIsActive(true);
		RELEASE_PART(m_pPart);
		//temp
		UTIL_SAFE_DELETE(m_pCache);
	}
	virtual void	OnAction(float dt)
	{
		if (!m_Invalidated)
		{
			if (!m_Triggered && !m_ZoomIn)
			{
				TriggerParticles();
				m_Triggered = true;
			}
			if (m_pPart) m_pPart->Tick(dt);	
		}
	}
	virtual void	OnCountdown()
	{
		if (!m_Invalidated)
		{
			if (m_ZoomIn)
			{
				// for zoom in, restore UI the moment animation is complete
				m_pTarget->SetIsActive(true);
				m_RenderSelf = true;
				m_RenderChildren = true;
			}
			else
			{
				// keep refreshing active status in case something else screws with it
				m_pTarget->SetIsActive(false);
			}
			if (!m_Triggered && m_ZoomIn)
			{
				TriggerParticles();
				m_Triggered = true;
				m_Ended = false;
				return;
			}
			else if (m_pPart && m_pPart->GroupC() && (m_pPart->GetGroup(0)->GetPartC() > 0))
			{
				m_Ended = false;
				return;
			}
		}
		UIMover::OnCountdown();
		//m_pTarget->SetIsActive(true); let destructor do this
	}
	void	TriggerParticles()
	{
		if (!m_pPart) return;

		math::Vec2 From = m_pTarget->GetPosition();
		math::Vec2 To = From + m_pTarget->GetSize();
		for (int i = 0; i < m_PartCount; i++)
		{
			m_pPart->TriggerParticlesOnLine( From, math::Vec2(To.x, From.y) );
			m_pPart->TriggerParticlesOnLine( math::Vec2(To.x, From.y), To );
			m_pPart->TriggerParticlesOnLine( To, math::Vec2(From.x, To.y) );
			m_pPart->TriggerParticlesOnLine( math::Vec2(From.x, To.y), From );
		}
	}

	void RenderCache(unsigned char r, unsigned char g, unsigned char b, unsigned char a, bool blend)
	{
		static Graphics::Vertex2D	CacheA[6];
		float Progress = m_Countdown.GetProgress();
		float Scale = m_ZoomIn ? Progress : 1.0f - Progress;
		math::Vec2 Pos = m_Pos + (0.5f * m_Size);
		math::Vec2 Size = Scale * m_Size;
		Pos -= (0.5f * Size);

		if (m_HasCached && m_pCache)
		{
			const math::Vec2	UV1 = math::Vec2(0.0f, 0.0f);
			const math::Vec2	UV2 = math::Vec2(1.0f, GraphicsUtil::HEIGHT * GraphicsUtil::InvW);
			GraphicsUtil::FillQuad(Pos, math::Vec2(Scale, Scale), UV1, UV2, ColorRGBA(r, g, b, a), CacheA);
			m_pCache->SetTextureStage(0);

			if (blend)
			{
				GetGraphics()->SetRenderState( Graphics::RS_SRC_BLEND,  Graphics::RV_BLEND_SRC_ALPHA );
				GetGraphics()->SetRenderState( Graphics::RS_DST_BLEND,  Graphics::RV_BLEND_ONE );
			}			
			GetGraphics()->DrawVertex2D(CacheA, 6);
			if (blend)		// reset
			{
				GetGraphics()->SetRenderState( Graphics::RS_SRC_BLEND,  Graphics::RV_BLEND_SRC_ALPHA );
				GetGraphics()->SetRenderState( Graphics::RS_DST_BLEND,  Graphics::RV_BLEND_INVSRC_ALPHA );
			}
		}
	}
		
	bool			m_HasCached;

	part2d::Part2D*	m_pPart;
	UISprite		m_PanelSprite;
	int				m_PartCount;
	math::Vec4		m_BaseColor;
	math::Vec2		m_Pos;
	math::Vec2		m_Size;
	bool			m_Triggered;
	bool			m_ZoomIn;
};

// hack: allows us to pack Delay, Time, Rotations into a single Vec3 to fit it under the max 5 arguments for LuaBind
class UIPartRotate : public UIMover
{
public:
	static UIMover* AddUIPartRotate(UIWidget* pTarget, math::Vec3 TimeSet, bool ZoomIn, const std::string& Part, const std::string& Action)
	{
		if (!pTarget) return NULL;
		UIPartRotate* pNew = new UIPartRotate(pTarget, TimeSet.x, TimeSet.y, (int)floorf(TimeSet.z), ZoomIn, Part);
		pNew->m_Action = Action;
		pTarget->AddMover(pNew);
		return pNew;
	};
	virtual void RenderForeground()
	{
		if (!m_Countdown.IsReady())
		{
			RenderCache(255,255,255,255,false);
		}

		if (m_pPart) m_pPart->Render(math::Vec2(), 1);
	}
private:
	UIPartRotate(UIWidget* pTarget, float Delay, float Time, int RotCount, bool ZoomIn, const std::string& Part) : UIMover(pTarget, Delay, Time, false),
			m_pPart(NULL),
			m_PartCount(0),
			m_Triggered(false),
			m_ZoomIn(ZoomIn)
	{
		COLOR c = pTarget->GetColor();
		m_BaseColor = math::Vec4( ColorGetRf(c), ColorGetGf(c), ColorGetBf(c), ColorGetAf(c) );

		m_RenderChildren = false;
		m_RenderSelf = false;
		m_RotCount = RotCount;

		//if (!Part.empty() && m_Part.Load(Part) && (m_Part.GroupC() > 0))
		if (!Part.empty())
		{
			m_pPart = PART_CACHE().GetPart(Part);
			if (m_pPart && (m_pPart->GroupC() > 0))
				m_PartCount = m_pPart->GetGroup(0)->GetMaxC() / 4;
		}

		if (UISprite* pSprite = pTarget->GetSprite(UIWidget::WS_MOUSE_OFF))
		{
			m_PanelSprite.Create(pSprite->GetFilename());
			m_PanelSprite.m_Pos = m_Pos = pTarget->GetPosition();
			m_PanelSprite.m_Size = m_Size = pTarget->GetSize();
			m_PanelSprite.m_Color = ColorfRGBA(m_BaseColor.x, m_BaseColor.y, m_BaseColor.z, m_ZoomIn ? 0.0f : m_BaseColor.w);
		}
		else
		{
			Invalidate();
			return;
		}

		pTarget->SetIsActive(false);

		// KEITH: create image cache
		//m_pCache = UIMoverCache::GetInstance()->GetImageCache(pTarget->GetSize());
		//temp
		m_pCache = GetGraphics()->CreateCache(1024, 1024, 32); // maybe should hardcode, since texture should be pow-of-2

		// KEITH: setup contents of cache
		if (m_pCache->BeginCache())
		{
			m_pCache->ClearCache( ColorRGBA(255,255,255,0) );
			
			math::Vec2 temppos;
			temppos.x = pTarget->GetPosition().x;
			temppos.y = pTarget->GetPosition().y;

			pTarget->SetPosition(math::Vec2(0.5f-m_Size.x/2.0f,0.5f-m_Size.y/2.0f));//render at center of imagecache for rotation
			pTarget->Render();
			pTarget->SetPosition(temppos);//reset position

			m_pCache->EndCache();
			m_HasCached = true;
		}
	}
	virtual ~UIPartRotate()
	{
		m_pTarget->SetIsActive(true);
		RELEASE_PART(m_pPart);
		//temp
		UTIL_SAFE_DELETE(m_pCache);
	}
	virtual void	OnAction(float dt)
	{
		if (!m_Invalidated)
		{
			if (!m_Triggered && !m_ZoomIn)
			{
				TriggerParticles();
				m_Triggered = true;
			}
			if (m_pPart) m_pPart->Tick(dt);	
		}
	}
	virtual void	OnCountdown()
	{
		if (!m_Invalidated)
		{
			if (m_ZoomIn)
			{
				// for zoom in, restore UI the moment animation is complete
				m_pTarget->SetIsActive(true);
				m_RenderSelf = true;
				m_RenderChildren = true;
			}
			else
			{
				// keep refreshing active status in case something else screws with it
				m_pTarget->SetIsActive(false);
			}
			if (!m_Triggered && m_ZoomIn)
			{
				TriggerParticles();
				m_Triggered = true;
				m_Ended = false;
				return;
			}
			else if (m_pPart && m_pPart->GroupC() && (m_pPart->GetGroup(0)->GetPartC() > 0))
			{
				m_Ended = false;
				return;
			}
		}
		UIMover::OnCountdown();
		//m_pTarget->SetIsActive(true); let destructor do this
	}
	void	TriggerParticles()
	{
		if (!m_pPart) return;

		math::Vec2 From = m_pTarget->GetPosition();
		math::Vec2 To = From + m_pTarget->GetSize();		
		for (int i = 0; i < m_PartCount; i++)
		{
			m_pPart->TriggerParticlesOnLine( From, math::Vec2(To.x, From.y) );
			m_pPart->TriggerParticlesOnLine( math::Vec2(To.x, From.y), To );
			m_pPart->TriggerParticlesOnLine( To, math::Vec2(From.x, To.y) );
			m_pPart->TriggerParticlesOnLine( math::Vec2(From.x, To.y), From );
		}
	}

	void RenderCache(unsigned char r, unsigned char g, unsigned char b, unsigned char a, bool blend)
	{
		static Graphics::Vertex2D	CacheA[6];
		float Progress = m_Countdown.GetProgress();
		float Scale = m_ZoomIn ? Progress : 1.0f - Progress;
		math::Vec2 Pos = m_Pos + (0.5f * m_Size);

		const float FullRot = 360.0f;
		float Rot = FullRot * m_RotCount * Progress;//degrees

		if (m_HasCached && m_pCache)
		{
			const math::Vec2	UV1 = math::Vec2(0.0f, 0.0f);
			const math::Vec2	UV2 = math::Vec2(1.0f, GraphicsUtil::HEIGHT * GraphicsUtil::InvW);

			GraphicsUtil::FillRotQuad(Pos, math::Vec2(Scale, Scale), Rot, UV1, UV2, ColorRGBA(r, g, b, a), CacheA);

			m_pCache->SetTextureStage(0);

			if (blend)
			{
				GetGraphics()->SetRenderState( Graphics::RS_SRC_BLEND,  Graphics::RV_BLEND_SRC_ALPHA );
				GetGraphics()->SetRenderState( Graphics::RS_DST_BLEND,  Graphics::RV_BLEND_ONE );
			}
			GetGraphics()->DrawVertex2D(CacheA, 6);
			if (blend)		// reset
			{
				GetGraphics()->SetRenderState( Graphics::RS_SRC_BLEND,  Graphics::RV_BLEND_SRC_ALPHA );
				GetGraphics()->SetRenderState( Graphics::RS_DST_BLEND,  Graphics::RV_BLEND_INVSRC_ALPHA );
			}
		}
	}
		
	bool			m_HasCached;

	part2d::Part2D*	m_pPart;
	UISprite		m_PanelSprite;
	int				m_PartCount;
	math::Vec4		m_BaseColor;
	math::Vec2		m_Pos;
	math::Vec2		m_Size;
	bool			m_Triggered;
	bool			m_ZoomIn;
	int				m_RotCount;
};

// hack: pack Delay and Time into Vec2
class UINumCounter : public UIMover
{
public:
	static UIMover*	AddNumCounter(UIWidget* pTarget, math::Vec2 TimeSet, int From, int To,  const std::string&  Action)
	{
		if (!pTarget) return NULL; 
		UINumCounter* pNew = new UINumCounter(pTarget, TimeSet.x, TimeSet.y);
		pNew->m_From = From;
		pNew->m_To	 = To;
		pNew->m_Action = Action;
		pTarget->SetTextA( lang::Lang::FormatCommaNumber(From) );
		pTarget->AddMover(pNew);
		return pNew;
	}
private:
	UINumCounter(UIWidget* pTarget, float Delay, float Time) : UIMover(pTarget, Delay, Time, true) {};
	virtual void	OnAction(float dt)
	{
		float Current = float(m_From) + (float(m_To - m_From) * m_Countdown.GetProgress());
		int Num = (int)floor(Current + 0.5f);
		m_pTarget->SetTextA( lang::Lang::FormatCommaNumber(Num) );

		if (Num == m_To) m_Countdown.ForceReady();
	}
	virtual void	OnCountdown()
	{
		m_pTarget->SetTextA( lang::Lang::FormatCommaNumber(m_To) );
		UIMover::OnCountdown();
	}
	int	m_From, m_To;
};

class UIDelayScript : public UIMover
{
public:
	static UIMover*	AddUIDelayScript(UIWidget* pTarget, float Time,  const std::string&  Action)
	{
		if (!pTarget) return NULL; 
		UIDelayScript* pNew = new UIDelayScript(pTarget, Time);
		pNew->m_Action = Action;
		pTarget->AddMover(pNew);
		return pNew;
	}
private:
	UIDelayScript(UIWidget* pTarget, float Time) : UIMover(pTarget, 0.0f, Time, true) {};
	virtual void	OnAction(float dt)
	{
	}
};

class UIDelayAction : public UIMover
{
public:
	static UIMover*	AddUIDelayAction(UIWidget* pTarget, float Time,  UIMover::MoverEvent	Event)
	{
		if (!pTarget) return NULL; 
		UIDelayAction* pNew = new UIDelayAction(pTarget, Time);
		pNew->m_pMoverEvent = Event;
		pTarget->AddMover(pNew);
		return pNew;
	}
private:
	UIDelayAction(UIWidget* pTarget, float Time) : UIMover(pTarget, 0.0f, Time, true) {};
	virtual void	OnAction(float dt)
	{
	}
};



//////////////////////////////////////
// our base-class
UIMover::UIMover(UIWidget*		pTarget, float Delay, float Time, bool DoDrop) : 
	m_pTarget(pTarget),
	m_Countdown(Time),
	m_Delay(Delay),
	m_DropOnExit(DoDrop),
	m_Invalidated(false),
	m_Instant(false),
	m_Ended(false),
	m_RenderChildren(true),
	m_RenderSelf(true),
	m_pMoverEvent(NULL),
	m_RunningScript(false)
	
{
	if (Time <= 0) m_Instant = true;
}


void	UIMover::Tick(float dt)
{
	if (m_Delay.IsReady())
	{
		if (m_Instant) 
		{
			OnCountdown();
			m_Ended = true;
		}
		else
		{
			m_Ended = m_Countdown.IsReady() && m_Delay.IsReady();
			OnAction(dt);
			if (m_Countdown.GetLimit() > 0 || m_Instant)
			{
				m_Countdown.Tick(dt);
				if (m_Countdown.IsReady() && !m_Invalidated)
					OnCountdown();
			}
		}
	} else
	{
		m_Delay.Tick(dt);
	}
}

void	UIMover::Invalidate()
{
	m_Countdown.ForceReady();
	m_Invalidated = true;
}


void	UIMover::OnCountdown()
{
	if (!m_Invalidated)
	{
		if (m_DropOnExit)
		{
			m_Ended = true;			
		}
		if (!m_Action.empty())
		{		
			m_RunningScript = true;			// don't delete on this state
			UIManager::Instance().ExecuteScript(m_Action);	
			m_RunningScript = false;		// can delete now
		}
		if (m_pMoverEvent)
		{
			m_pMoverEvent(this);
		}
	}
								// Otherwise the object may be Deleted
}




void	UIMover::Register(LuaPlus::LuaState* _pScript)
{
#ifdef _ENABLE_LUABIND
using namespace luabind;	
	lua_State * L = _pScript->GetCState ( );
	// functions for adding them

	luabind::module(L)							// CHIT: must register
    [											// Otherwise Luabind would not know UIMover 
    luabind::class_< UIMover >( "UIMover" )		// which return from it's children 			
	];													

	luabind::module(L)
	[
		luabind::def(  "AddUIMove",			&UIMove::AddUIMove  ,  discard_result ), 		
		luabind::def( "AddUIPan",		    &UIPan::AddUIPan , discard_result ) ,
		luabind::def(  "AddUISine",			&UISine::AddUISine ,discard_result ) ,
		luabind::def(  "AddUIXFade",		&UIXFade::AddUIXFade ,discard_result ) ,
		luabind::def(  "UIMoveProgress",	&UIMoveProgress::AddMoveProg ,discard_result ) ,
		luabind::def(  "UISetImage",		&UISetImage::AddSetImage ,discard_result ) ,
		luabind::def(  "AddUIScript",		&UIScript::AddUIScript ,discard_result ) ,
		luabind::def(  "AddUIHide",			&UIHide::AddUIHide ,discard_result ) ,
		luabind::def(  "AddUIPartFade",		&UIPartFade::AddUIPartFade ,discard_result ) ,
		luabind::def(  "AddUIPartZoom",		&UIPartZoom::AddUIPartZoom ,discard_result ) ,
		luabind::def(  "AddNumCounter",		&UINumCounter::AddNumCounter ,discard_result ) ,
		luabind::def(  "AddUIDelayScript",	&UIDelayScript::AddUIDelayScript ,discard_result ) 
	];
		
#endif //_ENABLE_LUABIND
}


//////////////
// HACK HACK HACK
// We really shouldn't implement this here; provide a real interface, or better yet abstract the actual 
// movers back out into the .h file?
UIMover*	UIWidget::AddUIPanMover(float Delay, float Time, const math::Vec2& To)
{	return UIPan::AddUIPan(this, Delay, Time, To, "");	}

UIMover*	UIWidget::AddUIMoveMover(float Delay, float Time, const math::Vec2& From, const math::Vec2& To)
{	return UIMove::AddUIMove(this, Delay, Time, math::Vec4(From.x, From.y, To.x, To.y), "");	}

UIMover*	UIWidget::AddUISineMover(const math::Vec3& X, const math::Vec3& Y)
{	return UISine::AddUISine(this, X, Y);	}

UIMover*	UIWidget::AddUIXFadeMover(float Delay, float Time, const math::Vec2& AlphaSet)
{	return UIXFade::AddUIXFade(this, Delay, Time, AlphaSet, "");	}

UIMover*	UIWidget::AddProgressMover(float Time, float From, float To)
{	return UIMoveProgress::AddMoveProg(this, Time, From, To, "");	}

UIMover*	UIWidget::AddSetImageMover(float Delay, const std::string& Image)
{	return UISetImage::AddSetImage(this, Delay, Image, "");	}

UIMover*	UIWidget::AddUIHideMover()
{	return UIHide::AddUIHide(this);	}

UIMover*	UIWidget::AddUIPartFadeMover(math::Vec2 TimeSet, math::Vec2 AlphaSet, const std::string& Part)
{	return UIPartFade::AddUIPartFade(this, TimeSet, AlphaSet, Part, "");	}

UIMover*	UIWidget::AddUIPartZoomMover(math::Vec2 TimeSet, bool ZoomIn, const std::string& Part)
{	return UIPartZoom::AddUIPartZoom(this, TimeSet, ZoomIn, Part, "");	}

UIMover*	UIWidget::AddUIPartScaleMover(math::Vec2 TimeSet, bool ZoomIn, const std::string& Part)
{	return UIPartScale::AddUIPartScale(this, TimeSet, ZoomIn, Part, "");	}

UIMover*	UIWidget::AddUIPartRotateMover(math::Vec2 TimeSet, int RotCount, bool ZoomIn, const std::string& Part)
{	return UIPartRotate::AddUIPartRotate(this, math::Vec3(TimeSet.x,TimeSet.y,(float)RotCount), ZoomIn, Part, "");	}

UIMover*	UIWidget::AddNumCounterMover(math::Vec2 TimeSet, int From, int To)
{	return UINumCounter::AddNumCounter(this, TimeSet, From, To, "");	}

UIMover*	UIWidget::AddDelayedActionMover(float Time, UIMover::MoverEvent	Event)
{	return UIDelayAction::AddUIDelayAction(this, Time, Event);	}


